package com.kmxd.ams.app.arrange.command;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveRuleMainSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.RuleFieldQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveRuleFieldListVO;
import com.kmxd.ams.client.archive.vo.ArchiveRuleMainListVO;
import com.kmxd.ams.client.arrange.IBoxSvc;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IProjectSvc;
import com.kmxd.ams.client.arrange.dto.InBoxDTO;
import com.kmxd.ams.client.arrange.vo.InBoxVO;
import com.kmxd.ams.client.event.log.DocumentLogEvent;
import com.kmxd.ams.client.system.ISysFondsSvc;
import com.kmxd.ams.client.system.vo.SysFondsVO;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.ArchOpEnum;
import com.kmxd.ams.core.common.enums.VolOrBoxTypeEnum;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.util.ConvertUtil;
import com.kmxd.ams.core.util.DynamicTableNameUtil;
import com.kmxd.ams.core.util.SecurityUtil;
import com.kmxd.ams.infra.archive.entity.ArchiveRuleMain;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.base.SystemBO;
import com.kmxd.ams.infra.arrange.entity.Box;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Project;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 装盒
 *
 * @author ZuoLG
 */
@Slf4j
@AllArgsConstructor
@Component
public class InBoxCmd {
  private final IBoxSvc boxSvc;
  private final IDocumentSvc documentSvc;
  private final IArchiveRuleMainSvc archiveRuleMainSvc;
  private final ArchCodeGenCmd archCodeGenCmd;
  private final ExtendDataCmd extendDataCmd;
  private final CheckCommonCmd checkCommonCmd;
  private final IArchiveTypeSvc archiveTypeSvc;
  private final IProjectSvc projectSvc;
  private final AmsEventPublisher amsEventPublisher;
  /**
   * 装盒 1.自动装盒 2. 加入指定盒
   *
   * @param req
   * @return
   */
  @Transactional(rollbackFor = Exception.class)
  public List<InBoxVO> inBox(InBoxDTO req) {
    // 1.自动装盒
    switch (VolOrBoxTypeEnum.convert(req.getNewBox())) {
      case AUTO:
        return autoInBox(req);
      case JOIN_SELECTED:
        if (ObjectUtil.isNull(req.getBoxId())) {
          throw BizTipException.instance(ErrorCode.VALIDATE_ERROR, "盒id不能为空");
        }
        return autoInBox(req);
      default:
        throw BizTipException.instance(ErrorCode.NOT_FOUND, "传入的装盒参数不正确！");
    }
  }

  private List<InBoxVO> autoInBox(InBoxDTO req) {
    Box qry = new Box();
    qry.setTableId(req.getBoxTableId());
    qry.setId(req.getBoxId());
    Box box = new Box();
    if (ObjectUtil.isNotEmpty(req.getBoxId())) {
      box =
          Optional.ofNullable(boxSvc.view(qry))
              .orElseThrow(() -> BizTipException.instance(ErrorCode.NOT_FOUND, "未找到盒信息"));
    }

    box.setManageStatus(
        ObjectUtil.isNotEmpty(box.getManageStatus())
            ? box.getManageStatus()
            : SecurityUtil.getManageStatus());
    List<InBoxVO> inBoxVOS = autoInBox(box, req);
    return inBoxVOS;
  }

  /**
   * 自动装盒公共方法
   *
   * @param req
   */
  private List<InBoxVO> autoInBox(Box box, InBoxDTO req) {
    // 判断年度是否一致
    BaseBO baseBO = BeanUtil.copyProperties(req, BaseBO.class);
    baseBO.setDocIds(req.getDocIds());
    baseBO.setTableId(req.getDocTableId());
    List<Document> docList = documentSvc.selectListByIds(baseBO);
    if (ObjectUtil.isEmpty(docList)) {
      Lists.newArrayList();
    }
    List<String> fondsList =
        docList.stream().map(SystemBO::getFondsCode).distinct().collect(Collectors.toList());
    if (ObjectUtil.isNotEmpty(fondsList) && fondsList.size() > 1) {
      throw BizTipException.instance(ErrorCode.FONDS_CODE_NOT_EQUAL, "全宗号不一致，无法装盒");
    }
    // 分组规则
    checkCommonCmd.checkBaseDoc(docList);
    // 此处直接取，规则校验必须所需一致
    Document doc = docList.get(0);
    Long oldBoxId = doc.getBoxId();
    // 查询规则
    ArchiveRuleMain ruleInfo = getRuleInfo(req.getArchTypeId(), doc.getYear(), box.getProjId());
    RuleFieldQueryDTO ruleFieldQueryDTO = new RuleFieldQueryDTO();
    ruleFieldQueryDTO.setArchTypeId(req.getArchTypeId());
    ruleFieldQueryDTO.setRuleId(ruleInfo.getId());
    ruleFieldQueryDTO.setHaveBox(true);

    // 查询规则下的字段信息
    List<ArchiveRuleFieldListVO> ruleFieldList =
        archiveRuleMainSvc.getRuleFieldByVersion(ruleFieldQueryDTO);
    if (ObjectUtil.isEmpty(ruleFieldList)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "未找到规则信息");
    }

    // 按分组字段条件，直接将数据分组， stream 嵌套group 会有展开的问题，如 Map<year,Map<classfy,List<Document>>>
    // 最佳方案，key为 分组信息，value为分好组的数据信息 Map<key,List<Document>>
    // 存在不符合分组规则的数据，直接返回前端错误数据信息
    // 校验是否符合盒规则
    checkCommonCmd.checkRuleDoc(ruleFieldList, docList);

    Integer max = docList.stream().mapToInt(Document::getDocSequence).max().orElse(0);
    Integer min = docList.stream().mapToInt(Document::getDocSequence).min().orElse(0);

    // 建立盒信息,新建盒
    if (VolOrBoxTypeEnum.AUTO.equals(VolOrBoxTypeEnum.convert(req.getNewBox()))) {
      box.setId(IdWorker.getId());
      box.setBoxSequence(req.getBoxSequence());
      box.setBoxNo(req.getBoxNo());
      box.setArchTypeId(req.getArchTypeId());
      box.setTableId(req.getBoxTableId());
      box.setBackWidth(req.getBackWidth());
      box.setYear(doc.getYear());
      box.setClassfy(doc.getClassfy());
      box.setClassfyName(doc.getClassfyName());
      box.setRetentionPeriod(doc.getRetentionPeriod());
      box.setSecurity(doc.getSecurity());
      box.setFilesNum(docList.size());
      box.setStatus(req.getStatus());
      box.setStartDocSequence(min);
      box.setEndDocSequence(max);
      box.setFondsId(
          ObjectUtil.isEmpty(box.getFondsId()) ? SecurityUtil.getFondsId() : box.getFondsId());
      String fondsCode = fondsList.size() == 1 ? fondsList.get(0) : SecurityUtil.getFondsCode();
      box.setFondsCode(fondsCode);
      //
      String fondsName;
      if (fondsList.size() == 1) {
        ISysFondsSvc sysFondsSvc = SpringUtil.getBean(ISysFondsSvc.class);
        SysFondsVO sysFondsVO =
            Optional.ofNullable(sysFondsSvc.view(box.getFondsId())).orElse(new SysFondsVO());
        fondsName = Optional.ofNullable(sysFondsVO.getName()).orElse(SecurityUtil.getFondsName());
      } else {
        fondsName = SecurityUtil.getFondsName();
      }
      box.setFondsName(fondsName);
      box.setDepartmentId(SecurityUtil.getLoginUser().getDeptId());
      box.setDepartment(SecurityUtil.getLoginUser().getDeptName());
      DynamicTableNameUtil.setTableName(req.getBoxTableId());
      // 将文件级中的数据同步到盒中
      // 查询规则下的字段信息
      ruleFieldQueryDTO.setHaveBox(null);
      List<ArchiveRuleFieldListVO> docRuleFieldList =
          archiveRuleMainSvc.getRuleFieldByVersion(ruleFieldQueryDTO);
      Box newBox = asyncArchInfoToBox(box, doc, docRuleFieldList);
      boxSvc.saveOrUpdate(newBox);
    } else {
      if (fondsList.size() == 1) {
        String docFondsCode = fondsList.get(0);
        String fondsCode = box.getFondsCode();
        if (!CharSequenceUtil.equals(docFondsCode, fondsCode)) {
          throw BizTipException.instance(ErrorCode.FONDS_CODE_NOT_EQUAL, "文件级、盒级 [全宗号] 不一致，无法装盒");
        }
      }
    }

    // 批量修改文件信息
    List<Document> updateDocList =
        docList.stream()
            .peek(
                docPeek -> {
                  docPeek.setTableId(req.getDocTableId());
                  docPeek.setArchTypeId(req.getArchTypeId());
                  docPeek.setBoxId(box.getId());
                  docPeek.setBoxNo(req.getBoxNo());
                  // 组卷日志
                  amsEventPublisher.publishEvent(
                      new DocumentLogEvent(docPeek, ArchOpEnum.BOXING, "装盒"));
                })
            .collect(Collectors.toList());
    // 执行批量修改
    DynamicTableNameUtil.setTableName(req.getDocTableId());
    documentSvc.updateBatchById(updateDocList);
    if (ObjectUtil.isNotEmpty(updateDocList)) {
      Document document = updateDocList.get(0);
      document.setArchTypeId(req.getArchTypeId());
      document.setTableId(req.getDocTableId());
      documentSvc.summary(document);
      if (!ObjectUtil.equals(oldBoxId, document.getBoxId())) {
        doc.setArchTypeId(req.getArchTypeId());
        doc.setTableId(req.getDocTableId());
        doc.setBoxId(oldBoxId);
        documentSvc.summary(doc);
      }
    }

    // 非新盒时重新统计
    if (!VolOrBoxTypeEnum.AUTO.equals(VolOrBoxTypeEnum.convert(req.getNewBox()))) {
      try {
        BaseBO baseNew = new BaseBO();
        baseNew.setArchTypeId(req.getArchTypeId());
        baseNew.setTableId(req.getDocTableId());
        baseNew.setBoxIds(StrUtil.join(",", box.getId()));
        List<Document> documentTmps = documentSvc.selectListByBoxIds(baseNew, true);

        Integer maxNew = documentTmps.stream().mapToInt(Document::getDocSequence).max().orElse(0);
        Integer minNew = documentTmps.stream().mapToInt(Document::getDocSequence).min().orElse(0);

        box.setStartDocSequence(minNew);
        box.setEndDocSequence(maxNew);
        box.setFilesNum(documentTmps.size());
        DynamicTableNameUtil.setTableName(req.getBoxTableId());
        boxSvc.saveOrUpdate(box);
      } catch (Exception ex) {
        ex.printStackTrace();
        log.info("统计新盒出现异常！");
      }
    }
    return Lists.newArrayList();
  }

  /**
   * 新建盒，将分组信息同步到盒里面
   *
   * @param box 盒
   * @param doc 文件信息
   * @return 新盒信息
   */
  private Box asyncArchInfoToBox(
      Box box, Document doc, List<ArchiveRuleFieldListVO> ruleFieldList) {
    if (ObjectUtil.isNull(doc)) {
      return box;
    }
    if (ObjectUtil.isEmpty(ruleFieldList)) {
      return box;
    }

    JSONObject obj = JSONUtil.parseObj(box);
    JSONObject docObj = JSONUtil.parseObj(doc);
    ruleFieldList.stream()
        .filter(ArchiveRuleFieldListVO::getEnableGroup)
        .forEach(
            rule -> {
              String field =
                  !StringUtils.isCamel(rule.getFieldCode())
                      ? StringUtils.underlineToCamel(rule.getFieldCode())
                      : rule.getFieldCode();

              if (field.equalsIgnoreCase(ArchConst.DOC_SEQUENCE)
                  || field.equalsIgnoreCase(ArchConst.ARCH_CODE)) {
                return;
              }
              String value = docObj.getStr(field);
              obj.set(field, value);
            });
    return obj.toBean(Box.class);
  }
  /**
   * 获取盒规则
   *
   * @param archTypeId
   * @param year
   * @return
   */
  public ArchiveRuleMain getRuleInfo(Long archTypeId, Integer year, Long projId) {

    // 普通盒
    RuleFieldQueryDTO qryRuleMain = new RuleFieldQueryDTO();
    qryRuleMain.setYear(year);
    qryRuleMain.setArchTypeId(archTypeId);
    if (Objects.nonNull(projId)) {
      ArchiveType archiveType = archiveTypeSvc.getById(archTypeId);
      Project qry = new Project();
      qry.setArchTypeId(archiveType.getId());
      qry.setTableId(archiveType.getProjTableId());
      qry.setId(projId);
      Project project = projectSvc.view(qry);
      if (Objects.nonNull(project)) {
        qryRuleMain.setRuleId(project.getArchRuleId());
      }
    }

    ArchiveRuleMainListVO ruleMainByVersion = archiveRuleMainSvc.getRuleMainByVersion(qryRuleMain);
    return ConvertUtil.toBean(ruleMainByVersion, ArchiveRuleMain.class);
  }
}
